//
// Copyright (c) 2009 All Right Reserved
//
// Stephen Toub
// stoub@microsoft.com
// 2009-01-01
// Contains Classes to represent MIDI events (voice, meta, system, etc).
using System;
using System.Diagnostics.Contracts;
using System.Globalization;
using System.IO;
using System.Text;
using LargoCommon.Music;
namespace LargoCommon.Midi
{
/// A MIDI event, serving as the base class for all types of MIDI events.
/// was abstract
[Serializable]
public class MidiEvent : ICloneable, IMidiEvent
{
#region Fields
/// The amount of time before this event.
private long deltaTime;
#endregion
#region Constructors
/// Initializes a new instance of the MidiEvent class.
/// The amount of time before this event.
public MidiEvent(long deltaTime) { //// protected
// Store the data
this.DeltaTime = deltaTime;
this.StartTime = deltaTime;
}
#endregion
#region Properties
///
/// Gets the type of the event.
///
///
/// The type of the event.
///
public string EventType {
get {
var eventType = this.GetType().ToString();
var dotPosition = eventType.LastIndexOf('.');
var pureType = eventType.Substring(dotPosition + 1);
//// string dotPureType = Path.GetExtension(eventType);
return pureType;
}
}
///
/// Gets or sets the start time.
///
///
/// The start time.
///
public long StartTime { get; set; }
/// Gets or sets the amount of time before this event.
/// General musical property.
public long DeltaTime { //// virtual (11/2010)
get => this.deltaTime;
set {
if (value < 0) {
throw new ArgumentOutOfRangeException(nameof(value), value, "Delta times must be non-negative.");
}
this.deltaTime = value;
}
}
///
/// Gets or sets the bar number.
///
///
/// The bar number.
///
public int BarNumber { get; set; }
///
/// Gets a value indicating whether IsVoiceNoteEvent.
///
/// General musical property.
public bool IsVoiceNoteEvent => this is VoiceAbstractNote voiceEvent && (voiceEvent.Channel != MidiChannel.DrumChannel);
///
/// Gets a value indicating whether IsMetaEvent.
///
/// General musical property.
public bool IsMetaEvent {
get {
var eventType = this.EventType;
switch (eventType) {
case "MetaText":
break;
case "MetaCopyright":
break;
case "MetaSequenceTrackName":
break;
case "MetaInstrument":
break;
case "MetaLyric":
break;
case "MetaMarker":
break;
case "MetaCuePoint":
break;
case "MetaProgramName":
break;
case "MetaDeviceName":
break;
default:
return false;
}
return true;
}
}
#endregion
#region Static methods
/// Splits a 14-bit value into two bytes each with 7 of the bits.
/// The value to be split.
/// The upper 7 bits.
/// The lower 7 bits.
//// internal
public static void Split14BitsToBytes(int bits, out byte upperBits, out byte lowerBits) {
lowerBits = (byte)(bits & 0x7F);
bits >>= 7;
upperBits = (byte)(bits & 0x7F);
}
#endregion
#region Public methods
/// Write the event to the output stream.
/// The stream to which the event should be written.
public virtual void Write(Stream outputStream) {
Contract.Requires(outputStream != null);
//// Write out the delta time
WriteVariableLength(outputStream, this.deltaTime);
}
#endregion
#region To String
/// Generate a string representation of the event.
/// A string representation of the event.
public override string ToString() {
var sb = new StringBuilder();
sb.Append(this.GetType().Name.PadRight(12));
sb.Append("\t");
var startString = " StartTime =" + this.StartTime.ToString(CultureInfo.CurrentCulture.NumberFormat);
sb.Append(startString.PadRight(12));
var deltaString = " DeltaTime =" + this.DeltaTime.ToString(CultureInfo.CurrentCulture.NumberFormat);
sb.Append(deltaString.PadRight(12));
return sb.ToString();
}
#endregion
#region Public Implementation of ICloneable
/// Creates a shallow copy of the MIDI event.
/// A shallow-clone of the MIDI event.
public IMidiEvent Clone() {
return (MidiEvent)this.MemberwiseClone();
}
#endregion
#region Implementation of ICloneable
/// Creates a shallow-copy of the object.
/// A shallow-clone of the MIDI event.
object ICloneable.Clone() {
return this.Clone();
}
#endregion
#region Internal methods
/// Combines two 7-bit values into a single 14-bit value.
/// The upper 7-bits.
/// The lower 7-bits.
/// A 14-bit value stored in an integer.
internal static int CombineBytesTo14Bits(byte upper, byte lower) {
// Turn the two bytes into a 14 bit value
int fourteenBits = upper;
fourteenBits <<= 7;
fourteenBits |= lower;
return fourteenBits;
}
#endregion
#region Protected methods
/// Converts an array of bytes into human-readable form.
/// The array to convert.
/// The string containing the bytes.
protected static string DataToString(byte[] givenData) {
if (givenData == null) {
return string.Empty;
}
var sb = new StringBuilder();
sb.Append("[");
for (var i = 0; i < givenData.Length; i++) {
//// If we're not the first byte, output a comma as a separator
if (i > 0) {
sb.Append(",");
}
//// Spit out the byte itself
sb.Append("0x");
sb.Append(givenData[i].ToString("X2", CultureInfo.CurrentCulture.NumberFormat));
}
sb.Append("]");
return sb.ToString();
}
/// Writes bytes for a long value in the special 7-bit form.
/// The stream to which the length should be written.
/// The value to be converted and written.
protected static void WriteVariableLength(Stream outputStream, long value) {
Contract.Requires(outputStream != null);
if (outputStream == null) {
return;
}
// TODO: Clean this up!
// Parse the value into bytes containing each set of 7-bits and a 1-bit marker
// for whether there are more bytes in the length
var buffer = value & 0x7f;
while ((value >>= 7) > 0) {
buffer <<= 8;
buffer |= 0x80;
buffer += value & 0x7f;
}
// Get all of the bytes in correct order
while (true) {
outputStream.WriteByte((byte)(buffer & 0xFF));
if ((buffer & 0x80) == 0) {
break;
} // if the marker bit is not set, we're done
buffer >>= 8;
}
}
#endregion
}
}